package service

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang/protobuf/proto"
	"github.com/gorilla/websocket"
	"github.com/levigross/grequests"
	cgapi "gitlab.vrviu.com/cloudgame_backend/access-public/grpc/com_pb"
	"gitlab.vrviu.com/inviu/backend-go-public/gopublic/config"
	"gitlab.vrviu.com/inviu/backend-go-public/gopublic/vlog"
	"strconv"
	"time"
)

// request servo/token
func (client *AreaClient) applyutoken(ctx context.Context) error {
	//vlog.Info("[aclient]用户 [%d] [%s] begin applyutoken succ ", client.Uid, client.FlowID)
	vlog.Infof("req:%v", map[string]interface{}{
		"app_userid":   client.Uid,
		"app_biz_type": client.bizType,
	})
	grsp, err := grequests.Post(
		client.UServoAddr+"/v1/servo/usertoken",
		&grequests.RequestOptions{
			JSON: map[string]interface{}{
				"app_userid":   client.UserID,
				"app_biz_type": client.bizType,
			},
		},
	)

	if err != nil {

		client.status, client.fail_msg = -1, err
		client.updateStatus(REQ_UTOKEN_ERR)
		return err
	}
	//vlog.Infof("走注册账号！！！")
	//fmt.Printf("LABEL is %v", grsp.String())
	client.updateStatus(REQ_UTOKEN_SUCC)
	var result TokenResult
	grsp.JSON(&result)
	//if err := grsp.JSON(&result); err != nil {
	//	fmt.Printf("err is %v", err)
	//	return err
	//}

	fmt.Printf("result", grsp.String())

	if result.Head.Code != 0 {
		client.status, client.fail_msg = -2, errors.New(result.Head.Msg)
		client.updateStatus(APPLY_UTOKEN_FAIL)
		return errors.New(result.Head.Msg)

	}
	client.updateStatus(APPLY_UTOKEN_SUCC)
	uuid, _ := strconv.ParseUint(result.Info.UserID, 10, 64)

	client.Uid = uuid
	client.Utoken = result.Info.Token
	vlog.Infof("[aclient]用户 [%d] [%s] applyutoken succ utoken(%s),status [%d]", client.Uid, client.FlowID, client.Utoken, client.status)
	return nil
}

// applyUserToken 申请用户 token
func (client *AreaClient) applyUserToken(ctx context.Context) error {
	reqData := map[string]interface{}{
		"app_userid":   client.UserID,
		"app_biz_type": client.bizType,
	}

	resp, err := grequests.Post(
		client.UServoAddr+"/v1/servo/usertoken",
		&grequests.RequestOptions{JSON: reqData},
	)
	if err != nil {
		client.status = REQ_UTOKEN_ERR
		client.fail_msg = err
		client.updateStatus(REQ_UTOKEN_ERR)
		return err
	}

	var result TokenResult
	if err := resp.JSON(&result); err != nil {
		return err
	}

	if result.Head.Code != 0 {
		client.status = APPLY_UTOKEN_FAIL
		client.fail_msg = errors.New(result.Head.Msg)
		client.updateStatus(APPLY_UTOKEN_FAIL)
		return client.fail_msg
	}

	client.updateStatus(APPLY_UTOKEN_SUCC)
	uuid, err := strconv.ParseUint(result.Info.UserID, 10, 64)
	if err != nil {
		vlog.Errorf("[aclient] parse user ID failed userId(%s) error(%s)", result.Info.UserID, err)
	}
	client.Uid = uuid
	client.Utoken = result.Info.Token

	vlog.Infof("apply user token succeeded, utoken(%s), status(%d)", client.Utoken, client.status)
	return nil
}

// setStatusAndFail 更新状态和错误信息
func (client *AreaClient) setStatusAndFail(status int, err error) error {
	client.updateStatus(status)
	client.fail_msg = err
	return err
}

// handleQueue 处理排队逻辑
func (client *AreaClient) handleQueue(ctx context.Context, result AreaResult, preAllocTime time.Time, req map[string]interface{}) error {
	client.updateStatus(ENter_UQ)
	qSession := result.Info.QInfo.SessionID
	doQFlag := IS_QUEUE
	segTime := 0

	for doQFlag == IS_QUEUE {
		select {
		case <-ctx.Done():
			vlog.Infof("client exit, wg done")
			return client.setStatusAndFail(EXIt_UQ_FORCE, errors.New("force quit from UQ"))

		default:
			time.Sleep(time.Duration(segTime) * time.Second)
			queueAddr := fmt.Sprintf("https://%s/v1/uqueue/get_rank_info?uuid=%d&queue_session_id=%s", result.Info.QInfo.Addr, result.Info.UID, qSession)

			queueResp, err := grequests.Get(queueAddr, &grequests.RequestOptions{
				RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
			})
			if err != nil || queueResp.StatusCode != 200 {
				vlog.Errorf("apply queue failed, url: %s, status_code: %d, err: %v",
					queueAddr, queueResp.StatusCode, err)
				continue
			}

			var queueInfo UnionRankResult
			if err := queueResp.JSON(&queueInfo); err != nil {
				vlog.Errorf("parse queue response failed, url: %s, err: %v", queueAddr, err)
				continue
			}

			// 继续排队
			if queueInfo.Info.QueueFlag == 0 {
				vlog.Infof("still in queue, rank/total: %d/%d, queue_session: %s, seg_time: %d, uid: %d, flowId: %s",
					queueInfo.Info.Rank, queueInfo.Info.Total, qSession, queueInfo.Info.SegInterval, client.Uid, client.FlowID)
				segTime = int(queueInfo.Info.SegInterval)
				continue
			}

			// 出队并重新申请
			if queueInfo.Info.QueueFlag == 1 {
				if queueInfo.Head.Msg == "session not found" {
					vlog.Errorf("session not found, session: %s, uid: %d, flowId: %s", queueInfo.Info.FlowID, client.Uid, client.FlowID)
				}

				againApplyResp, err := grequests.Post(
					client.UAccessAddr+"/v1/union/apply",
					&grequests.RequestOptions{
						RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
						JSON:           req,
						Headers: map[string]string{
							"Gateway-Auth-Biztype": strconv.FormatUint(client.bizType, 10),
						},
					},
				)
				if err != nil || againApplyResp.StatusCode != 200 {
					vlog.Errorf("re-apply failed, err: %v,  uid: %d, flowId: %s", err, client.Uid, client.FlowID)
					continue
				}

				var result2 AreaResult
				if err := againApplyResp.JSON(&result2); err != nil {
					return err
				}

				vlog.Infof("re-apply response: %s, duration: %v, uid: %d, flowId: %s ", againApplyResp.String(), time.Since(preAllocTime), client.Uid, client.FlowID)
				if result2.Head.Code == 0 {
					result = result2
					client.updateStatus(Exit_UQ)
					return nil
				}
				qSession = result2.Info.QInfo.SessionID
			}
		}
	}
	return nil
}

// handleUnionApplyResponse 处理 union apply 的响应逻辑
func (client *AreaClient) handleUnionApplyResponse(ctx context.Context, resp *grequests.Response, result AreaResult, preAllocTime time.Time, req map[string]interface{}) error {
	respTime := time.Now()

	// 处理状态码 429（限流）
	if resp.StatusCode == 429 {
		vlog.Errorf("union apply current-limiting, url: %s, status_code: %d, err: %v, duration: %v", client.UAccessAddr+"/v1/union/apply", resp.StatusCode, nil, respTime.Sub(preAllocTime))
		return client.setStatusAndFail(CURRENT_LIMITING, errors.New("APPLY CURRENT_LIMITING"))
	}

	// 处理排队情况（Code 3400034）
	if result.Head.Code == 3400034 && result.Head.Msg != "disable enqueue" {
		return client.handleQueue(ctx, result, preAllocTime, req)
	}
	if result.Head.Code == 3400034 && result.Head.Msg == "disable enqueue" {
		vlog.Warnf("gid (%d) disable enqueue,apoloo union-access biz-cus.gid-cus biz-31281.gid-def.disable_burst_enqueue", client.Gid)
		return errors.New(fmt.Sprintf("%d disable enqueue", client.Gid))
	}

	// 处理其他游戏运行中（Code 3400004）
	if result.Head.Code == 3400004 {
		vlog.Errorf("union apply failed, resp code: %d, msg: %s, uid: %d, flowId: %s ", result.Head.Code, result.Head.Msg, client.Uid, client.FlowID)
		return errors.New("another game running")
	}

	// 计费异常alloc charge id failed（Code 3400010）
	if result.Head.Code == 3400010 {
		vlog.Errorf("union apply failed, resp code: %d, msg: %s, uid: %d, flowId: %s ", result.Head.Code, result.Head.Msg, client.Uid, client.FlowID)
		return errors.New("alloc charge id failed")
	}

	// 处理成功但地址为空的情况（Code 0）
	if result.Head.Code == 0 && result.Info.Info.AccessAddr == "" {
		vlog.Errorf("apply area failed, addr empty uid: %d, flowId: %s ", client.Uid, client.FlowID)
		return client.setStatusAndFail(APPLY_FAIL, errors.New("apply area failed"))
	}

	return nil
}

// request union/apply
func (client *AreaClient) unionApply(ctx context.Context) error {
	preAllocTime := time.Now()
	req := map[string]interface{}{
		"ugid":             client.Gid,
		"area_type":        client.AreaType,
		"dev_id":           "4ec1eb3e640f55fe12d5093fd8589702",
		"dev_type":         5,
		"flow_id":          client.FlowID,
		"os_type":          4,
		"prelaunch_param":  client.getPrelaunchParam(),
		"queue_level":      0,
		"sdk_version":      3053035,
		"rset_id":          client.ClientController.RsetID,
		"check_game_state": true,
	}
	if !client.ClientController.IsAutoResist {
		req["utoken"] = client.Utoken
		req["uuid"] = client.Uid
	} else {
		req["app_user_id"] = client.UserID
	}

	resp, err := grequests.Post(
		client.UAccessAddr+"/v1/union/apply",
		&grequests.RequestOptions{
			RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
			JSON:           req,
			Headers: map[string]string{
				"Gateway-Auth-BizType": strconv.FormatUint(client.bizType, 10),
			},
		},
	)
	if err != nil || resp.StatusCode != 200 {
		vlog.Errorf("union apply failed, url: %s, uid: %d, flowId: %s, err: %v", client.UAccessAddr+"/v1/union/apply", client.Uid, client.FlowID, err)
		return client.setStatusAndFail(REQ_APPLY_ERR, err)
	}

	var result AreaResult
	if err := resp.JSON(&result); err != nil {
		vlog.Errorf("failed to parse union apply response: %v, uid: %d, flowId: %s", err, client.Uid, client.FlowID)
		return err
	}
	formattedJSON, err := json.MarshalIndent(result, "", "  ")
	if err != nil {
		vlog.Errorf("Parsed result:%s  err(%s)", string(formattedJSON), err)
	}

	vlog.Infof("union apply successfully, resp(%s)", formattedJSON)
	// 处理响应逻辑
	if err := client.handleUnionApplyResponse(ctx, resp, result, preAllocTime, req); err != nil {
		return err
	}

	// 成功情况
	client.updateStatus(APPLY_SUCC)
	client.Atoken = result.Info.Token
	client.AreaAddr = fmt.Sprintf("%s:%d", result.Info.Info.AccessAddr, result.Info.Info.AccessPort)
	client.FlowID = result.Info.FlowID
	client.Uid = result.Info.UUID
	return nil
}

// request union/apply
func (client *AreaClient) applyarea(ctx context.Context) error {
	preAllocTime := time.Now()

	req := map[string]interface{}{
		"ugid":             client.Gid,
		"area_type":        client.AreaType,
		"dev_id":           client.DevID,
		"dev_type":         5,
		"flow_id":          client.FlowID,
		"os_type":          3,
		"prelaunch_param":  client.getPrelaunchParam(),
		"queue_level":      0,
		"sdk_version":      3053035,
		"rset_id":          client.ClientController.RsetID,
		"check_game_state": true,
	}

	if !client.ClientController.IsAutoResist {
		req["utoken"] = client.Utoken
		req["uuid"] = client.Uid
	} else {
		req["app_user_id"] = client.UserID
	}
	respTime := time.Now()
	vlog.Infof("[aclient] [%d]client do apply，req is %v", client.Uid, req)
	resp, err := grequests.Post(
		client.UAccessAddr+"/v1/union/apply",
		&grequests.RequestOptions{
			RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
			JSON:           req,
			Headers: map[string]string{
				"Gateway-Auth-BizType": strconv.FormatUint(client.bizType, 10),
			},
		},
	)
	if err != nil {
		vlog.Errorf("[aclient] [%d]  unionApply() error ！！！.url is (%v) status_code(%d) errmsg:%v,time waste:%v", client.Uid, client.UAccessAddr+"/v1/union/apply", resp.StatusCode, err, respTime.Sub(preAllocTime))
		client.fail_msg = err
		client.updateStatus(REQ_APPLY_ERR)
		return errors.New("APPLY ERROR")
	}

	var result AreaResult
	if err := resp.JSON(&result); err != nil {
		return err
	}
	infoJSON, _ := json.MarshalIndent(result.Info, "", "  ")
	if resp.StatusCode == 429 {
		vlog.Errorf("[aclient] [%d]  unionApply() current-limitingnionApply current-limiting！！！.url is (%v) status_code(%d) errmsg:%v,time waste:%v", client.Uid, client.UAccessAddr+"/v1/union/apply", resp.StatusCode, err, respTime.Sub(preAllocTime))
		client.updateStatus(CURRENT_LIMITING)
		client.fail_msg = err
		return errors.New("APPLY CURRENT_LIMITING")
	} else if result.Head.Code == 3400034 {
		client.updateStatus(ENter_UQ)
		qSession := result.Info.QInfo.SessionID
		doQFlag := IS_QUEUE
		segTime := 0
		for doQFlag == IS_QUEUE {
			select {
			case <-ctx.Done():
				doQFlag = EXIt_UQ_FORCE
				vlog.Infof("[aclient] [%d] [%s]ClientExit wg done1", client.Uid, client.FlowID)
				client.updateStatus(EXIt_UQ_FORCE)
				client.fail_msg = errors.New("force quit from UQ")
				return errors.New("force quit from UQ")

			default:
				time.Sleep(time.Duration(segTime) * time.Second)
				queueAddr := fmt.Sprintf("https://%s/v1/uqueue/get_rank_info?uuid=%d&queue_session_id=%s", result.Info.QInfo.Addr, result.Info.UID, qSession)

				queueResp, err := grequests.Get(queueAddr, &grequests.RequestOptions{
					RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second},
				)

				var queue_info UnionRankResult
				if err != nil || queueResp.StatusCode != 200 {
					vlog.Errorf("user:%v apply_queue fail! url is:%s,statues_code is:%v,err is :v%", client.UserID, queueAddr, queueResp.StatusCode, err)
					continue
				}

				if err := queueResp.JSON(&queue_info); err != nil {
					vlog.Errorf("user:%v apply_queue fail! url is:%s,err is:%v", client.UserID, queueAddr, err)
					continue
				}
				//继续轮询名次
				if queue_info.Info.QueueFlag == 0 {
					vlog.Infof("apply_queue_info succ!用户[%v] still in Q,rank/total=%v/%v,queue_sseion:%v,seg_time=%v", client.Uid, queue_info.Info.Rank, queue_info.Info.Total, qSession, queue_info.Info.SegInterval)
					segTime = int(queue_info.Info.SegInterval)
					continue
				}

				if queue_info.Info.QueueFlag == 1 {
					if queue_info.Head.Msg == "session not found" {
						vlog.Errorf("session not found,session is %s", queue_info.Info.FlowID)

					}

					againApplyResp, err := grequests.Post(
						client.UAccessAddr+"/v1/union/apply",
						&grequests.RequestOptions{
							RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
							JSON:           req,
							Headers: map[string]string{
								"Gateway-Auth-Biztype": strconv.FormatUint(client.bizType, 10),
							},
						},
					)

					if err != nil || againApplyResp.StatusCode != 200 {

						vlog.Errorf("do re_apply fail!")
						continue
					}
					vlog.Infof("outter_queue applyArea resp (%s),time waste:%v,code:%v", againApplyResp.String(), respTime.Sub(preAllocTime), err)
					var result2 AreaResult
					if err := againApplyResp.JSON(&result2); err != nil {
						return err
					}
					if result2.Head.Code == 0 {
						result = result2
						doQFlag = NO_QUEUE
						client.updateStatus(Exit_UQ)

						break
					} else {

						qSession = result2.Info.QInfo.SessionID
						continue
					}

				}

			}

		}
	} else if result.Head.Code == 3400004 {
		vlog.Errorf("[aclient] [%d]  unionApply() resp code(%d),msg(%s)", client.Uid, result.Head.Code)
		return errors.New("another game running")
	} else if result.Head.Code == 0 && result.Info.Info.AccessAddr == "" {
		infoJSON, err := json.MarshalIndent(result.Info, "", "  ")
		if err != nil {
			vlog.Errorf("Failed to marshal Info to JSON: %v\n", err)
		}
		vlog.Errorf("[aclient] [%d] [%s] applyarea failed addr empty, result.Info(%s)", client.Uid, client.FlowID, infoJSON)
		client.updateStatus(APPLY_FAIL)
		client.fail_msg = errors.New("apply area failed")
		return errors.New("apply area failed")
	}
	vlog.Infof("unionApply() time waste:%v,restult:%v，esult.Info(%s)", respTime.Sub(preAllocTime), result.Head.Code, infoJSON)
	client.Uid = result.Info.UUID
	client.updateStatus(APPLY_SUCC)
	client.Atoken = result.Info.Token
	client.AreaAddr = fmt.Sprintf("%s:%d", result.Info.Info.AccessAddr, result.Info.Info.AccessPort)
	client.FlowID = result.Info.FlowID

	vlog.Infof("[aclient] [%d] [%s] applyarea succ addr(%s), status [%d]", client.Uid, client.FlowID, client.AreaAddr, client.status)
	return nil
}

// ws connect
func (client *AreaClient) wsConnect(ctx context.Context, ch chan error) {
	var err_msg string
	url := fmt.Sprintf("wss://%s/v1/endpoint?token=%s&dev_type=%d&dev_id=%s", client.AreaAddr, client.Atoken, client.DevType, client.DevID)
	d := &websocket.Dialer{}
	ws, _, err := d.Dial(url, nil)
	if err != nil {
		client.updateStatus(WS_CONN_FAIL)
		client.fail_msg = err
		vlog.Errorf("[aclient] [%d] [%s] ws connect failed, err(%v),client.status(%d)", client.Uid, client.FlowID, err, client.status)
		ch <- client.fail_msg
		close(ch)
		panic(err)
	}
	client.updateStatus(WS_CONN_SUCC)
	client.WSConn = ws

	client.heartbeat()
	//接收退出消息
	go func() {
		select {
		case <-ctx.Done():
			//判断是否曾经退出过，退出过则什么都不做
			if !client.ClientExit {
				client.ClientExit = true
				vlog.Infof("[aclient] [%d] [%s]ClientExit wg done2", client.Uid, client.FlowID)
				client.wg.Done()
				client.updateStatus(ForceQuit)
				vlog.Infof("[aclient] [%d] [%s]ClientExit force exit", client.Uid, client.FlowID)
			}
			return
		}
	}()

	go func() {
		defer func() {
			if err := recover(); err != nil {
				vlog.Errorf("panic:%v", err)
			}
		}()
		for {
			if client.ClientExit {
				client.exit()
				vlog.Warnf("[aclient] [%d] [%s] client exit, queue(%v) queueTime(%v) alloc(%v) allocTime(%v) duration(%v)", client.Uid, client.FlowID, client.QueueState, client.PreAllocTime.Sub(client.QueueTime), client.AllocState, client.AllocTime.Sub(client.PreAllocTime), time.Since(client.StartTime))
				break
			}

			message, err := client.receiveMessage()
			if err != nil {
				vlog.Errorf("[aclient] [%d] [%s] websocket disconnect: err(%v)", client.Uid, client.FlowID, err)
				if client.status == ForceQuit {
					break
				}
				return
			}
			client.handleMessage(ctx, &message)
		}

		//开始缓存结果数据
		if client.status == ForceQuit {
			client.fail_msg = errors.New("client force quit by Tester")
		}

		if client.status == 9 {
			client.fail_msg = errors.New("User Queueing call exit")
		}
		//处理err_msg为空的情况
		if client.fail_msg == nil {
			err_msg = "No Error"
		} else {
			err_msg = client.fail_msg.Error()
		}
		AreaClientInfo := CreateAreaClientInfo(client.Uid, client.Gid, client.bizType, client.AreaType, client.DevType, client.status, 1, client.Atoken, client.Utoken, client.DevID, client.FlowID, client.AreaAddr, client.UServoAddr, client.UAccessAddr, client.UserID, err_msg, client.AllocTime.Sub(client.PreAllocTime).Seconds())

		aci, err := json.Marshal(AreaClientInfo)
		if err != nil {
			vlog.Errorf("Error marshaling JSON:", err)
		}
		vlog.Infof("[aclient] [%d] [%s]AreaClientInfo(%v)", client.Uid, client.FlowID, AreaClientInfo)
		rds_ctx := context.Background()
		if _, err := client.redisCli.HSet(rds_ctx, client.Result_key, client.UserID, aci).Result(); err != nil {
			vlog.Errorf("save redis fail:(%v)", err)

		}

		if client.fail_msg != nil {
			ch <- client.fail_msg

			close(ch)
		}
		vlog.Infof("[aclient] [%d] [%s]ClientExit wg done3", client.Uid, client.FlowID)
		if client.status != ForceQuit {
			client.wg.Done()
		}

		vlog.Infof("[aclient] [%d] [%s]ClientExit wg done4", client.Uid, client.FlowID)

		if err := client.WSConn.Close(); err != nil {
			return
		}
	}()

	vlog.Infof("[aclient] [%d] [%s] ws connect succ atoken(%s), status [%d]", client.Uid, client.FlowID, client.Atoken, client.status)

	return
}

// 处理ws收到的消息
func (client *AreaClient) receiveMessage() (message cgapi.CGMessage, err error) {
	typ, data, err := client.WSConn.ReadMessage()
	if err != nil {
		vlog.Errorf("[aclient] WSConn ReadMessage err (%s)", err)
	}

	if typ == websocket.BinaryMessage {
		err := proto.Unmarshal(data, &message)
		if err != nil {
			vlog.Errorf("[aclient] websocket BinaryMessage err (%s)", err)

		}
	} else if typ == websocket.CloseMessage {
		if c, k := err.(*websocket.CloseError); k {
			vlog.Errorf("[aclient] [%d] [%s] recv close event. code(%d) msg(%s)", client.Uid, client.FlowID, c.Code, c.Text)
		}
	}
	return
}

// ws发送消息
func (client *AreaClient) sendMessage(message *cgapi.CGMessage) error {
	if client.WSConn == nil {
		vlog.Errorf("[aclient] [%d] [%s] ws not connect", client.Uid, client.FlowID)
	}

	data, _ := proto.Marshal(message)

	if err := client.WSConn.WriteMessage(websocket.BinaryMessage, data); err != nil {
		vlog.Errorf("[aclient] [%d] [%s] ws send msg err ", client.Uid, client.FlowID)
	}

	return client.WSConn.WriteMessage(websocket.BinaryMessage, data)
	// err = websocket.Message.Send(client.wsConn, data)
}

// FlowRunningStatus 定义流程运行状态的结构体
type FlowRunningStatus struct {
	FlowID string `json:"flow_id"`
	Status int    `json:"status"`
	Uid    uint64 `json:"uid"`
	IsDone int    `json:"is_done"`
}

// updateStatus 更新流程状态并存储到 Redis
func (client *AreaClient) updateStatus(newStatus int) {
	if client.status != ForceQuit {
		client.status = newStatus

		flowStatus := FlowRunningStatus{
			FlowID: client.FlowID,
			Status: newStatus,
			Uid:    client.Uid,
			IsDone: 0,
		}

		data, err := json.MarshalIndent(flowStatus, "", "  ")
		if err != nil {
			vlog.Errorf("[aclient] failed to marshal flow status: %v", err)
		}
		ctx := context.Background()
		if err := client.redisCli.HSet(ctx, client.Result_key, client.UserID, data).Err(); err != nil {
			vlog.Errorf("[aclient] failed to save to redis, key: %s, userID: %s, error: %v", client.Result_key, client.UserID, err)
		}

		vlog.Infof("[aclient] updated status for flowID: %s, userID: %s, status: %d", client.FlowID, client.UserID, newStatus)
	}

}

// 发送pb协议：ReqLaunchGame
func (client *AreaClient) launchGame(ctx context.Context) {
	client.sendRequest(cgapi.CmdType_ReqLaunchGame, client.getCGReqLaunchGame())
	client.updateStatus(LAUNCH_GAME_SENDED)
}

// 发送心跳
func (client *AreaClient) heartbeat() {
	// 启动心跳
	go func() {
		logTicker := time.NewTicker(2 * time.Minute)
		defer logTicker.Stop()
		for {
			select {
			case <-time.Tick(time.Second * 5):
				if !client.ClientExit {
					client.sendRequest(cgapi.CmdType_ReqHeartbeat, client.getHeartBeat())
				}
			case <-logTicker.C:
				if !client.ClientExit {
					vlog.Infof("[aclient] [%d] [%s] send heartbeat", client.Uid, client.FlowID)
				}
			case <-client.HBSigCh:
				return
			}
		}
	}()
}

// 处理闲时消息的逻辑：发送pb消息
func (client *AreaClient) sendRequest(cmdType cgapi.CmdType, request proto.Message) {
	if client.ClientExit && cmdType != cgapi.CmdType_ReqExitGame {
		return
	}
	var reqMessage cgapi.CGMessage
	reqMessage.Cmdtype = uint32(cmdType)
	reqMessage.Version = 1
	reqMessage.Payload, _ = proto.Marshal(request)

	if err := client.sendMessage(&reqMessage); err != nil {
		client.ClientExit = true
		vlog.Warnf("[aclient] [%d] [%s] send request failed, type(%s)", client.Uid, client.FlowID, cmdType.String())
	} else if cmdType != cgapi.CmdType_ReqHeartbeat {
		vlog.Infof("[aclient] [%d] [%s] send request succ, type(%s), status [%d]", client.Uid, client.FlowID, cmdType.String(), client.status)
	}

	if time.Since(client.StartTime) >= time.Second*time.Duration(client.HoldTime) {
		client.ClientExit = true
		vlog.Infof("[aclient] [%d] [%s] client hold expire", client.Uid, client.FlowID)
	}
}

// 处理各种server传来的消息
func (client *AreaClient) handleMessage(ctx context.Context, message *cgapi.CGMessage) {

	switch cgapi.CmdType(message.Cmdtype) {
	case cgapi.CmdType_RspHeartbeat:
		var rsp cgapi.CGRspHeartbeat
		err := proto.Unmarshal(message.Payload, &rsp)
		if err != nil {
			return
		}
	case cgapi.CmdType_RspLaunchGame:
		var rsp cgapi.CGRspLaunchGame
		err := proto.Unmarshal(message.Payload, &rsp)
		if err != nil {
			return
		}
		msg := proto.MarshalTextString(&rsp)
		if rsp.GetResult().GetErrcode() == 0 {
			client.updateStatus(LAUNCH_GAME_SUCC)
			vlog.Infof("[aclient] [%d] [%s] launch game succ,msg(%s)", client.Uid, client.FlowID, msg)
		} else {
			client.updateStatus(LAUNCHGAME_FAIL)
			client.fail_msg = errors.New(" launch game failed")
			if client.RelaunchTimes > 0 {
				time.Sleep(3)
				client.launchGame(ctx)
				client.RelaunchTimes -= 1
				vlog.Infof("[aclient] [%d] [%s] do relaunch game succ,", client.Uid, client.FlowID)
			} else {
				client.ClientExit = true
				vlog.Errorf("[aclient] [%d] [%s] launch game failed. code(%d) msg(%s)", client.Uid, client.FlowID, rsp.GetResult().GetErrcode(), rsp.GetResult().GetMessage())
			}
		}

	case cgapi.CmdType_NtfGameStatus:
		var ntf cgapi.CGNtfGameStatus
		err := proto.Unmarshal(message.Payload, &ntf)
		if err != nil {
			return
		}

		if ntf.GameStatus == cgapi.GameStatus_GameStatusStartFailure {

			client.ClientExit = true
			client.updateStatus(GAMESTART_FAIL)
			client.fail_msg = errors.New(" start game failed")
			vlog.Infof("[aclient] [%d] [%s] start game failed!!!", client.Uid, client.FlowID)
		} else if ntf.GameStatus == cgapi.GameStatus_GameStatusAllocSuccess {
			vlog.Infof("[aclient] [%d] [%s]  alloc Success, msg(%s)", client.Uid, client.FlowID, proto.MarshalTextString(&ntf))

			if client.ClientController.IsStreaming {
				client.launchGSM(&ntf)
			}
			vlog.Infof("[aclient] [%d] [%s] alloc succ", client.Uid, client.FlowID)
			client.AllocState = true
			client.AllocTime = time.Now()

			client.updateStatus(ALLOC_SUCC)
			vlog.Infof("[aclient] [%d] [%s] game AllocSuccess, 分配vm:(%d),client_status(%d)", client.Uid, client.FlowID, ntf.GameResource.Vmid, client.status)
		} else if ntf.GameStatus == cgapi.GameStatus_GameStatusAllocQueuing {
			client.QueueState = true
			client.QueueTime = time.Now()
			client.AllocTime = time.Now()

			client.updateStatus(AREA_QING)
			vlog.Infof("[aclient] [%d] [%s] game allocqueueing,client_status(%d)", client.Uid, client.FlowID, client.status)

		} else if ntf.GameStatus == cgapi.GameStatus_GameStatusAllocPrepare {
			if client.status < ALLOC_PREPARE {
				client.AllocTime = time.Now()
				client.updateStatus(ALLOC_PREPARE)
				vlog.Infof("[aclient] [%d] [%s] game AllocPrepare,client_status(%d)", client.Uid, client.FlowID, client.status)
			}
		}
		if ntf.GameStatus == cgapi.GameStatus_GameStatusReadyRender {
			client.updateStatus(READY_RENDER)
			if client.ClientController.IsQuickOut {
				vlog.Infof("[aclient] [%d] [%s] delay exit %v second", client.Uid, client.FlowID, client.ClientController.DelayExit)
				time.Sleep(time.Duration(client.ClientController.DelayExit) * time.Second)
				client.ClientExit = true
			}
			vlog.Infof("[aclient] [%d] [%s] GameStatus_GameStatusReadyRender,client_status(%d)", client.Uid, client.FlowID, client.status)

		}
	case cgapi.CmdType_NtfSystem:
		var ntf cgapi.CGNtfSystem
		err := proto.Unmarshal(message.Payload, &ntf)
		if err != nil {
			return
		}

		vlog.Infof("[aclient] [%d] [%s] recv ntf system, msg(%s)", client.Uid, client.FlowID, proto.MarshalTextString(&ntf))
	}

}

func (client *AreaClient) launchGSM(ntf *cgapi.CGNtfGameStatus) {
	preAllocTime := time.Now()
	launch_url := fmt.Sprintf("https://%s:%d/launch", ntf.GameResource.ResourceDomain, ntf.GameResource.ResourceIpv4Portseg*100+4)
	launch_data := map[string]interface{}{
		"imeType":                    0,
		"keyboardType":               0,
		"flowId":                     client.FlowID,
		"gId":                        client.Gid,
		"startFlag":                  0,
		"userId":                     strconv.Itoa(int(client.Uid)),
		"connectionType":             "webrtcVideo",
		"forceAudioDataChannel":      0,
		"lpMultichannel":             1,
		"videoDataType":              "mp4.h264",
		"supportVideoCodecProfiles":  []int{8, 1},
		"avgBitrateratio":            100,
		"restartGame":                0,
		"deviceWidth":                0,
		"deviceHeight":               0,
		"desiredWidth":               1920,
		"desiredHeight":              1080,
		"maxFps":                     30,
		"packetSize":                 768,
		"clientType":                 "web-pc",
		"fecRepairPercent":           5,
		"clientSupportHevc":          0,
		"withHWCursor":               false,
		"requestUseServerCursorData": true,
		"idrPeriod":                  60,
		"videoLevel":                 3,
		"vibration":                  true,
		"clientId":                   "Stress_tester",
		"forwardIp":                  ntf.GameResource.ResourceIpv4Address,
		"forwardIpV6":                "",
		"forwardPort":                ntf.GameResource.ResourceIpv4Portseg * 100,
		"forwardPortV6":              ntf.GameResource.ResourceIpv4Portseg * 100,
		"sdkVersion":                 "3.53.35",
		"joinMethod":                 0,
		"targetUuid":                 0,
		"aSessionId":                 ntf.GameResource.ASessionId,
		"vSessionId":                 ntf.GameResource.VSessionId,
		"cSessionId":                 ntf.GameResource.CSessionId,
		"gamescreen":                 "landscape",
		"deviceId":                   client.DevID,
		"clientNetworkType":          40,
		"gamePlayType":               0,
		"hostMethod":                 0,
		"customerType":               0,
		"supportHashCursorIco":       1,
	}
	launchResp, err := grequests.Post(
		launch_url,
		&grequests.RequestOptions{
			RequestTimeout:     time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
			JSON:               launch_data,
			InsecureSkipVerify: true,
		},
	)
	respTime := time.Now()
	vlog.Infof("[aclient] [%d] [%s] client launch GSM succ.url is (%s) req is %v,rsp is %v", client.Uid, client.FlowID, launch_url, launch_data, launchResp.String())

	if err != nil || launchResp.StatusCode != 200 {
		vlog.Errorf("[aclient] [%d] [%s] client launch GSM failed.url is (%s) status_code(%d) errmsg:%v,time waste:%v", client.Uid, client.FlowID, launch_url, launchResp.StatusCode, err, respTime.Sub(preAllocTime))
		client.updateStatus(-3)
		client.fail_msg = err
		client.ClientExit = true
		client.updateStatus(LAUNCHFALURE)
	}

}

// 处理退出
func (client *AreaClient) exit() {

	client.sendRequest(cgapi.CmdType_ReqExitGame, client.getExitGame())

	vlog.Infof("[aclient] [%d] [%s] send exit,client_status(%d)", client.Uid, client.FlowID, client.status)
}
